#include <stdlib.h>
#include <string.h>

#include <AL/al.h>
#include <AL/alc.h>

#include "../core.h"
#include "../log.h"
#include "../types.h"

#include "host.h"

#define NUMBUFFER 64
#define SAMPLE_MAX 65472

typedef struct Buffer
{   
    struct Buffer *next;
    ALushort buf[SAMPLE_MAX * 2];
    u16 bufSize;
    u8 onceQueued;
    ALenum format;
    ALuint bufferId;
} Buffer;

typedef struct {
    Buffer list[NUMBUFFER];
    Buffer *writeBuf, *readBuf;
    u8 curBuf;
    u8 queueBuf;
    ALuint src;
} SoundChannel;

ALuint lastBuf = -1;

u32 numSounds = 0;
u32 curSoundId = 0;

SoundChannel *alChans;
u8 numChannels;

void host_initAudio(u8 numChans)
{
    ALCdevice  *dev = alcOpenDevice(NULL);
    ALCcontext *ctx = alcCreateContext(dev, NULL);
    u8 i, j;

    numChannels = numChans;

    alcMakeContextCurrent(ctx);

    alChans = malloc(sizeof(SoundChannel) * numChannels);
    for (i = 0; i < numChannels; i++)
    {
        alGenSources(1, &alChans[i].src);
        for (j = 0; j < NUMBUFFER; j++) {
            alChans[i].list[j].onceQueued = 0;
            alChans[i].list[j].next = &alChans[i].list[j + 1];
            alGenBuffers(1, &alChans[i].list[j].bufferId);
        }
        alChans[i].list[NUMBUFFER - 1].next = &alChans[i].list[0];
        alChans[i].readBuf  = &alChans[i].list[0];
        alChans[i].writeBuf = &alChans[i].list[0];
        alChans[i].curBuf = 0;
        alChans[i].queueBuf = 1;
    }
}

void host_freeAudio()
{
    ALCcontext *ctx = alcGetCurrentContext();
    ALCdevice  *dev = alcGetContextsDevice(ctx);
    u8 i, j;

    for (i = 0; i < numChannels; i++)
    {
        alDeleteSources(1, &alChans[i].src);
        for (j = 0; j < NUMBUFFER; j++)
            alDeleteBuffers(1, &alChans[i].list[j].bufferId);
    }
    free(alChans);

    alcMakeContextCurrent(NULL);

    alcDestroyContext(ctx);
    alcCloseDevice(dev);
}

u8 host_queueBuffer(void *sndBuf, u32 count, u8 stereo, u8 channel)
{
    ALenum format;
    SoundChannel *chan;
    Buffer *buf;
    if (stereo)
        format = AL_FORMAT_STEREO16;
    else
        format = AL_FORMAT_MONO16;

    cs_enter(&sndTh_mtx);
    chan = &alChans[channel];
    buf = chan->writeBuf;
    _log(DBG, AUDIO, "Queueing buffer %d (channel %d)", chan->curBuf, channel);
    chan->curBuf++;
    buf->format = format;
    buf->bufSize = count * (stereo ? 2 : 1);
    memcpy(buf->buf, sndBuf, buf->bufSize * sizeof(ALushort));
    chan->writeBuf = buf->next;

    if (chan->curBuf >= chan->queueBuf) {
        cs_leave(&sndTh_mtx);
        return 1;
    }
    cs_leave(&sndTh_mtx);
    return 0;
}

void host_audioUpdate()
{
    u8 i;
    for (i = 0; i < numChannels; i++)
    {
        SoundChannel *chan = &alChans[i];
        ALint processed, queued;
        ALuint usedBufs[NUMBUFFER];
        processed = 0;
        alGetSourcei(chan->src, AL_BUFFERS_QUEUED, &queued);
        /* Make sure the current buffer id isn't used currently */
        while (queued + chan->curBuf >= NUMBUFFER - 1)
        {
            alGetSourcei(chan->src, AL_BUFFERS_PROCESSED, &processed);
            if (processed)
                alSourceUnqueueBuffers(chan->src, processed, usedBufs);
            alGetSourcei(chan->src, AL_BUFFERS_QUEUED, &queued);
        }
        if (processed >= chan->queueBuf - 2 && chan->queueBuf < NUMBUFFER - 2)
            chan->queueBuf++;
        if (chan->curBuf != 0) /* Needed for the first time! */
        {
            ALint state;
            _log(DBG, AUDIO, "=> Playing %d buffers!", chan->curBuf);
            do
            {
                Buffer *buf = chan->readBuf;
                alBufferData(buf->bufferId, buf->format, buf->buf, buf->bufSize * sizeof(ALushort), 44100);
                alSourceQueueBuffers(chan->src, 1, &buf->bufferId);
                chan->readBuf = buf->next;
            } while (--chan->curBuf);
            alGetSourcei(chan->src, AL_SOURCE_STATE, &state);
            if (state != AL_PLAYING)
                alSourcePlay(chan->src);
        }
    }
}

